So in our last lecture, we filled out our custom tween service.
We called Better Tween to allow us to create tweens on the server that have their effects applied.
After all, clients have played the tween on their end.
Now we can go ahead and get started creating and scripting the custom door system that will be responsible
for handling all of these nice doors in our game, and we're going to be using our better tween module
script to create smooth tweens that get applied on both the client and on the server.
So we can go ahead and go into our server script service and open up the module script that will be
responsible for handling the doors, which is our door service.
Inside of here, we're going to need three services.
One is going to be replicated storage.
We're going to need the server storage service.
And lastly, we're going to need the server script service.
Now we're also going to be using the service to act as a class for creating new door objects.
So every single door in our game is stored in a folder called doors.
And when this service gets started, we're going to loop through all of the doors in our game and create
and set up these custom door objects based on the models and based on the attributes that are given
for these different doors.
So as you can see, some of these doors have different attributes.
For example, this locked door that we have in our basement requires a key, and the key name is provided
here as well.
We also have added an attribute on these doors to define whether or not some of them are one way.
So for example, the doors inside of our stalls only go one way, and we've defined a one way rotation
for these doors into which direction they must rotate.
So inside of the service we're going to have the table to represent the service.
But we're also going to have a table to represent the meta table that will be attached to custom objects
created by our service.
And we can return our door service at the end here.
Now inside of our meta table.
We need to, of course, make sure that we set the underscore underscore index meta method equal to
the door service meta table.
And then from this point we can start referencing a bunch of other different things.
The first thing that we can reference is our better tween module script.
And that's going to be inside of server script service dot server dot modules dot better tween.
We can then also reference our update guy event.
And we're going to be using this to communicate messages to players when they try to interact with something
like a lock door.
And that means we're going to have to also require our G actions enum.
That way we can stay consistent with the actions we want to send to the clients.
So G actions enum.
There is an event inside of server storage called Door Event.
And this is a Bindable event.
And we're going to use this event to allow different server scripts, no matter what script they're
located in, to reference this event and control doors from their end without having to interact with
this service or require this service directly.
Some other references we're going to make are some sound instances that we have stored in replicated
storage.
So we're going to use these sound instances to play them on the doors directly.
So when we open a door we'll play this sound.
When we close the door we'll play this one.
And when we unlock a door we'll play this sound.
Just like that.
So we'll make a reference to each one of those.
We'll have a door open, sound, replicated storage instances, sounds, door open.
We'll have our door close sound.
And we're also going to have a reference to our door unlock sound.
Okay.
Now we're going to have to create a table and I'm going to call it current doors.
And this will store all of the door objects that we have currently created in our game.
And this is serving as our cache for storing all of these door objects.
And the last but not least, we're going to have one constant.
And this constant is going to define how long we should tween the doors for.
And we're going to call this door move duration.
And we're just going to set it to one second.
You can of course adjust this value to whatever you'd like.
But I found one second to work best for all of our doors.
Now, some private functions that we're going to need for our door service is one to play sounds.
So we'll call it play sound and we'll get past a sound instance here.
Sound.
And we'll get told the parent we want to set for this sound.
We'll have a function for when a prompt gets triggered in our game, or when a prompt on our door gets
triggered so we can call it on triggered.
And then we're going to need a function to listen to that bindable event that might get fired by other
scripts in our game, so we can call it on door event, and this will get passed an action and arguments
to go along with that action.
Now, since this service is also going to be acting as a class, we of course need a constructor.
So inside of our door service we can create a new constructor.
And all this function is going to be passed is a model that represents the door.
So we're basically just going to loop through every single door inside of our Krusty Krab.
Pass the model to this constructor, and this will take care of the rest for us.
So we can create a table in here called self.
This will represent our object.
And then inside of self we can set a reference to the door model which is equal to door model.
We can set the type equal to the door models.
Attribute of get attribute door type.
We can set the property of one way equal to the door model, get attribute one way if it has one.
If it doesn't, then we can just set it to false using the Or statement.
If this door is one way, then we can set a property called one way rotation inside of our object.
So door model get attribute one way rotation.
We'll then create a references to the two different prompts that are going to be in our door.
So we'll have prompt number one equal to door model dot prompt part.
So there's a part inside of each one of our doors called prompt part.
And it has attachments in here that store the two different proximity prompts.
Because the plan for our doors is will have a proximity prompt on this side and on this side.
And depending on which one gets triggered, we'll open the door in the appropriate direction.
So if we activate the prompt on this side, the door is going to swing that way.
But if we trigger the prompt on this side, then the door is going to swing the other way.
That way the door actually doesn't rotate into the player.
So we can reference our prompt part and get attachment one, which represents the first part, and get
the proximity prompt inside of it.
And then we're going to do the exact same thing for prompt number two.
So we'll just copy this to prompt two and reference attachment number two.
We're going to have a property in here to keep track of whether or not the door is locked.
So we could do door model get attribute locked or we set it to false by default.
We'll do self dot requires key equal to door model get attribute requires key or the default will be
false as well.
And if self dot requires key is true.
Then we can set the property of key name equal to the door model.
Get attribute key name.
Then we're going to have a couple other properties, one that will keep track of whether or not the
door is open by default.
That is false.
And then we'll keep track of whether or not the door is moving, and we'll set that to false as well.
Now based on the two different type attributes that we set up here, we're going to have to do something
slightly different when referencing the hinges in the door.
So basically, there's an invisible part back here that the door is going to rotate along with.
So this hinge is the part that we need to rotate to move our door.
Now single doors have one hinge, while double doors like these ones are going to have two hinges,
one for each side.
So if the type of our door is equal to single, then all we need to do is create one property that will
refer to the original hinge, and we need to store the original hinges sea frame.
That way we can always keep track of what the original position was for when the hinge needs to close.
So we can call this original hinge sea frame.
And that's going to be equal to self dot door model dot hinge.
And we get the sea frame of the hinge.
Otherwise.
If the type of our door is equal to double, then we need to create two of these properties.
So we'll just copy this.
We'll have original hinge one see frame.
And then we can have originals, hinge two C frame, and then we'll just make this one refer to hinge
number one.
And this one will refer to hinge number two.
Now that we have all these properties set up, we need to connect some functions to events inside of
our proximity prompts so we can do self dot prompt one dot triggered.
We want to connect a lambda function to this.
That will get the player that triggered the proximity prompt.
And then we can call our on triggered function and pass.
Um, we want to pass the object itself.
We want to pass which prompt got triggered.
So that way uh, our door will know which direction to swing in.
So self dot prompt one is the one that got triggered here.
And then we're going to pass the player that uh activated this prompt.
And then we can basically just copy this and do the exact same thing for prompt number two.
But instead we need to pass self dot prompt two as the prompt that got triggered.
Then at the very end here we can set the meta table on our object to the door service meta table.
And then we can insert our object into the current doors table and then return self at the very end
of our constructor if we need to.
And I really do not like this Roblox bug.
This is a new bug that's happening.
Now another function that I want this service to have and not the objects themselves, is I want to
have a function to be able to get a door object based on a model provided.
So we're going to call this door service get door object from model.
So in case we need to grab a door object but we only have the model, then we could do is we could get
the model passed to this function.
And then we'll just loop through every single door in our current doors table.
And we're going to check if this door door model is equal to the model passed to this function.
If it is, then we can just return this door object.
Otherwise we won't return anything at all.
Now, what are some functions that doors in our door service need to have?
Well, first we need a function to actually open a door.
So door service made a table open.
And this function will get passed the prompt that was triggered.
So we could just call it triggered prompt.
We need a function to close a door.
So door service meta table close.
We need a function to be able to lock a door.
So door service.
A table lock.
And of course, we'll also need a function to unlock a door.
We'll need a function to disable the prompts or enable the prompts in a door.
So door service meta table enable prompts.
And then disable prompts.
So actually I'll just copy this, paste that here and rename this to Disable prompts.
And I believe that's all of the functions that we're going to need for our door objects.
The last functions that the service itself is going to need of course is an initialize function and
a start function.
So door service init.
And then door service start.
And we can actually fill out these two functions because they should be rather uncomplicated.
So when we want to initialize the service, all we're going to do is we're going to listen to our door
event dot.
Event and connect our on door event function to it.
And then for the start, all we're going to do in here is we're going to loop through every single door
model that is inside of the workspace.
Dot Krusty Krab dot doors.
We'll get all those children in there, and then we're just going to use our door service dot new constructor
and pass this door model.
And that way we are enabling all the functionality for this particular door.
So I guess the first function we could go ahead and fill out is our on door event here.
So let's go ahead and scroll up to that private function.
Basically what's going to happen here is that another server script is going to request to do some kind
of action to one of our doors.
And of course, since only the door objects are being stored in this script, uh, that other script
isn't going to know exactly the door object, uh, for this particular door model.
So instead, it'll pass to us the model of the door they'd wish to effect.
And then that's why we use our get door object from model function to, you know, find the particular
object for that door.
So what we could do is we could get the door from our door service.
Get door object from model and inside of the arguments passed to this function, we could just basically
have a key that represents the door model we wish to effect.
And then we can check if this door object exists or not.
So if it doesn't then we'll just return.
Otherwise we can go ahead and check the actions requested.
So what we could actually do is we could actually create another enum specifically for our door service.
So inside of service script service, um, inside of our door service, we could create a new module
script in here.
And we can call this door action enums or Door Actions enum.
And let me just find an enum that we've already filled out.
So if we go into replicated storage, um, modules, enums, let me just open up one of these random
ones.
I'll just copy all of this and then just paste it in here.
And the different actions that we could do is we could open a door.
We could close a door.
We could lock a door.
And we can unlock a door.
And I think that's all the actions that we're going to need for now.
So we can go to our door service and basically just require this door actions enum.
So when we refer to our door event, we can just make a reference to Door Actions enum.
And we can just require and refer to the script itself and then Door Actions enum, because it is a
child of this script.
And then we can go back to our on door event function and listen to these different actions.
So if the action requested is equal to the door actions enum dot open door, then we can use this door
object and call the open function on it.
We can do the same thing if the action is equal to door actions.
Enemy closed door do door close if the action is equal to door actions.
Enum dot lock door.
We can, you know, do door lock.
And if the action is equal to door actions enum unlocked door, then we could do door unlock.
Perfect.
Now of course, when we open a door we need to provide which prompts to pass to our open door function,
because this function needs to know which way to swing the door in.
So in this args table, we could also provide a proximity prompt that will act as the prompt that was
technically triggered.
But basically I guess the next functions we can go ahead and fill out are our different functions on
the doors.
So for example, what do we want to do to open a door.
Well, first, before we even open a door we want to check if our door is already open.
So if self dot is open then we're just going to return.
We don't need to open a door that's already open.
Another thing we need to check is if our door is already moving.
So if self dot is moving.
Then we also need to return.
And I believe I need to rename that property here.
So let me do that real quick.
So make sure you change that to is moving.
Otherwise, if all checks out, we can set self dot is moving equal to true and do the same thing for
is open because we're going to be opening the door.
And then we're going to do is we're going to create a new tween info using tween info dot new.
We're going to pass our door move duration.
And then for the easing style I found that an enum dot easing style of sign looks best on our doors.
But of course you can change that to whatever you'd like.
And now when we open the door, we need to check whether or not our door object itself is of the type
single or double.
So if our door is of the type single, then what we need to do is we need to check which prompt was
triggered.
So if the triggered prompt is equal to self dot prompt one.
Then we need to rotate our door along the y axis.
-90 degrees.
So if we go ahead and take a look at like one of our doors here, for example let's look at our prompt
part.
So attachment one is on this side.
So this is attachment or proximity prompt one.
If this gets triggered right then we need to go to our hinge.
And we need to rotate this -90 degrees along the y axis.
So if I go to see frame here and I rotate it -90 degrees, we want our door to open.
That way if the prompt over here gets triggered, then we want to open our door the other way.
Right.
Back into our door service script.
If the triggered prompt is self dot prompt one, then what we need to do is we need to use our better
tween, uh, module script to create a tween on this door.
So I'm going to actually create a variable.
I'm going to call it goal.
And inside of this section we're going to set goal equal to what we want to effect is the C frame of
our hinge.
So we can do self dot door model dot hinge dot c frame.
And we want to add a C frame angles of zero degrees on the x axis, -90 on the y axis and then zero
on the z axis.
And remember c frame dot angles accepts only radians.
So we need to convert this using the math dot rad function.
Perfect.
That's going to be the goal if the prompt triggered was prompt one.
Else if the triggered prompt is equal to self dot prompt number two, then we need to do the exact opposite.
So we need to set this number to 90 degrees instead.
Now another thing we need to check is whether or not our door is one way.
So if, uh, self dot one way.
Then we need to update the goal of the keyframe to instead be the value that was stored in the attribute
for the door.
So for example, with our stall doors, we've defined it that the one way rotation has to be -90 degrees
on the y axis.
So we need to replace this with our self dot one way rotation value.
And we need to of course also convert this into radians.
Alrighty.
And if we were successful in creating a goal table, then we can use our better tween module script
and use our tween function in there.
We want to pass the self dot door model.
That hinge.
We'll pass our tween info that we created, and we're going to pass the goal table that we just created.
And this module script is going to go ahead and take care of all of the animation to give on the client.
So the clients are going to play this tween on their end.
And after that tween is over, then the effects get applied on the server.
So that way we can see the new property applied on both the client and on the server.
Now we need to check.
If the self dot type of our door is equal to double.
If it is, then we need to create two different tweens for both hinges.
So that means we can create a goal for hinge one, and we can create a goal for hinge number two.
So if the triggered prompt is equal to self dot prompt one, then that means the first hinge.
We need to rotate a -90 degrees on the y axis.
And the second hinge we need to rotate 90 degrees on the y axis because they are rotating in opposite
directions.
So goal hinge one can be set to basically what we did here.
But instead the key frame is going to be equal to self dot model dot hinge one dot key frame.
We're going to copy this.
We're going to do the same thing for goal hinge two.
But instead we're going to refer to.
Hinge two.
And then we need to change this to 90 degrees.
Else if the triggered prompt is equal to self dot prompt number two.
Then we're just going to copy this, but we're just going to reverse the role of the numbers.
So this one can be 90 and this one can be -90.
After that if we have goal hinge one and goal hinge two.
Then we can use our better tween to create a new tween on self dot door model dot hinge one.
Pass our tween info and then pass goal for hinge number one.
Copy this and do the exact same thing for hinge number two.
But let me just verify that we got the numbers correct here real quick.
So if we go to our double door, for example, right here, let me select this.
If we take a look at our hinge one.
Oh actually our hinge one is on the left side and not the right side.
Never mind.
So my bad, I thought the hinge one was on the right side.
So instead we actually need to flip these values again.
So this one has to be 90.
This one has to be -90.
And this one has to be 90.
So that means if we activate prompt one on this side hinge number one needs to be rotated 90 degrees
here.
And then hinge number two needs to be rotated -90 degrees just like that.
If the prompt is activated on the other side, then this one needs to be rotated to 90 degrees, and
this one needs to be rotated to -90 degrees.
There we go.
Beautiful.
Now that we have done the tween on the door, we now need to play the sound on the door as well so we
can use our play sound function.
We can pass which sound instance we want to play, which is the door open sound.
And we can pass the parent for where the sound should play.
So we could do self dot door model.
Dot prompt part will play it from the prompt part.
So we'll fill this function out in a moment.
But what we can do after this is we can wait for the door move duration to end.
And once that ends we can set self dot is moving equal to false.
And while our door is actually moving, what we could do before we actually tween the doors is we could
use the Self Disable Prompts function to disable all the prompts on the door, and then when the door
stops moving, then we can re-enable all of the prompts on the door.
So let's go ahead and actually fill out these two functions real quick.
Inside of enable prompts we do self dot prompt one dot enabled equal to true and copy this to the exact
same thing for prompt number two.
And then for disabling the prompts.
It's very easy.
We'll just change these values to false.
Okay, some other functions.
I guess we could just go ahead and fill out real quick is our unlock and lock function.
So these ones are going to be very easy.
All we need to do is set self dot locked equal to true.
And then in here self dot locked equal to false.
Very simple.
The next function we can go ahead and fill out is our Playsound function.
Now, if you remember in our original lobby, we had that module script on the client that received
information from the server onto playing sounds.
Now we're going to do the exact same thing here in our game as well.
We have a sounds handler on the client that's going to handle playing sounds given from the server.
But since we don't have this filled out right now and we're not going to focus on filling it out yet,
this function here, the code that I'm going to put in here is going to act as a placeholder for now.
So we're going to change this in the future.
But just to make sure that everything in our scripts are working well, we could do quickly is just
make a clone of the sound instance that is passed to this function.
We could set the parent of this clone equal to the parent pass of the function, and then what we could
do is set the property like uh sound clone dot play on remove equal to true.
And then we're just going to destroy the sound.
And that way we can test whether or not our door sounds are working.
The next function we can go ahead and fill out is our on triggered function, which gets executed when
a prompts on our doors are triggered.
Because if you remember, we pass to this function the object, the prompt that was triggered and the
player that triggered the prompt.
So what we could do inside of our on triggered function is first, check whether or not our door is
moving.
So if self dot is moving is true, then we're just going to return because we don't want to care.
Oh, and let me define self here in the top.
Let me define the triggered prompt here as well.
And then let me define the player.
Okay, if self not locked is true, then what we can do is we can use our update, GUI event and fire
to this particular player that hey, the door is locked so we can use the GUI actions enum of small
message and we can give some text to this player and say something like the door is locked and then
we can just return out of our function again.
The next thing we could do is we could check if self dot requires key is true.
And if it is, we need to make sure that this player actually doesn't have the key required for this
door.
So if this player does not have the key we required equipped.
So if not player dot character find first child and we pass the self dot key name.
If they don't have this tool with this particular name, then we're also going to use our update guy
event and tell them that the door requires the key.
And we can just concatenate this with the self dot key name.
Also, if we do require the key and the player actually has the key in their inventory, then what we
could do is we could do self dot requires key set that to false because the door is unlocked.
We don't need to require a key anymore.
And then we're going to set self dot locked equal to true.
And then we're going to set on the door model itself.
We're going to set attribute locked equal to true.
And this is because we want to stop the door from moving any further once it's been unlocked, because
inside of our basement, there is one locked door back here that requires a key, and when the player
unlocks it and the door opens, we don't want to allow the players to be able to close the door, because
that means they could just hide in here and the squid where that is hunting them down won't be able
to run and get them in here, or they won't be able to see them either.
Our squid word will be able to pass through doors, but our door won't be able to see them when we fire
raycast because it's just going to hit this door object.
So we don't want them to be able to, you know, close or shut this door once it's been opened.
So that's why we're going to lock the door.
And then we can also play our sound of door unlock sound.
And the parent again is going to be self-taught door model dot for this one we could just do the door
itself.
Now when our prompt is triggered, we need to check whether or not our door is open or closed.
So if self dot is open then we need to use the close function on our door.
Otherwise we'll just use the self dot open function on our door, and we're going to pass the triggered
prompt to our function.
Now the next function we can go ahead and fill out is our close function.
So what do we want to do to close a door?
Well, first we need to check whether or not the door is already closed.
So if not self dot is open.
So if the door is already closed, then we'll just return.
If the door is moving, then we're also going to return.
And then what we're going to do is set self dot is moving equal to true.
Set self dot is open equal to false.
We're going to disable all of the prompts in our door.
And then we can basically just do the exact same things that we did in here.
I'm just going to copy all of this.
We get our tween info, blah blah blah.
And we can go ahead and make this more concise because we don't have to worry about a triggered prompt.
So if the door is single, uh, what we're going to do is we're just going to create a goal and set
the key frame for our hinge to be equal to self dot original, uh, hinge key frame.
Remember that property we made on this door object.
And then we can use our better tween module script to create a tween on self dot door model dot hinge,
pass our tween info and then pass our goal.
And then we're just going to copy this to the exact same thing here.
But we'll have a goal for a hinge one.
And this is going to be original hinge one key frame.
Copy this.
Do the exact same thing for hinge number two.
And then inside of better tween.
We're going to tween first on hinge one.
Pass goal hinge one.
Copy this.
Do the exact same thing for hinge number two.
And then once that's done, we can go ahead and play the sound door closed.
Sound.
Uh, the parent for this can be self dot door model.
Dot prompt part.
And the reason I'm setting it to the prompt part is because we don't know if this door is double or
single, so we can't really set it to be a parent of one of the doors in there, because there might
be two.
There might be one, we don't know.
So we're just going to set it to the prompt part.
Since there's only one of those.
Again, we're going to wait for the door move duration.
And after that is up we're going to set self.
Dot is moving back to false and enable the prompts again back on our door.
And guess what.
That's it.
Our door system should be complete.
So let's go ahead and test it out.
If we go into our game and hit play here, we're going to have to go through our intro scene.
Of course.
So we're going to have to wait for this to go by.
Okay.
Let's go up to our door here and let's see if we can open it.
So we hold down E and we see the effects got applied.
But unfortunately we did get an error on our on client tween event.
So let's go ahead and actually see what is going on here.
We're getting an error of unable to cast value to objects are.
And that's why we made a typo in our tween handler.
And here we're actually referencing the global instance data type instead of referring to the instance
that's passed to this function.
So capitalization is always important.
Make sure you put that as a lowercase I and not a capital I.
Very important.
Okay, but our door did open.
We did hear the sound.
We just didn't get it tweened because we had some goofy error in our tween handler on the client.
But let's go ahead and test this out again now that we fixed it.
Okay, let's go ahead and test our door out now.
So if we go up to our door, we hold down e.
Beautiful.
Our door opens just as we expect.
If we hold down E on the prompt again, it should allow us to close the door.
Door returns back to its original position, and we can go ahead and try out the prompt on the other
side to see if our door will open the other direction.
So we hold down E perfect.
Our door opens the other way and we can close it.
Now let's go ahead and try out some of these other doors like this one.
So if we hold down E perfect, our door opens, we can close it and we should be able to open it from
the other direction just like that.
Amazing.
The next door is we can go ahead and test out our our stall doors.
So let's see if they open only one way they should come swing out this direction.
So perfect.
Let's say if I close myself in here and I open it again.
Perfect.
Only moving one way, just as we would expect.
And currently I'm viewing the game on the server and as you can see, the effects of our tweens have
been applied as well on the server.
So if I go back to the client and I go and interact with this door and close it, as you can see here,
back on the server, our door has been updated and it is now closed.
So it seems that our game is coming together quite nicely so far.
Keep up the good work and I'll see you in the next lecture.